/**
 * BatchRoundRobinLQ is a batch which retrieves the leads belonging to the queues 
 * and assign them to the queue memebers.
 */
global class BatchRoundRobinLQ implements Database.Batchable<sObject> {
	
	// UserId prefix to differentiate between user and groups in GroupMembers Object
	private String userPrefix = '005';
	
	//declaration of string to store the query to retrieve leads
	global String query;
	
	/**Constructor which creates the query to retrieve the leads belonging to the queues on 
	 *which round robin assignment should be performed.
	 */
	global BatchRoundRobinLQ(){		
    	map<string,string> queueList = new map<string,string>();
        for(All_Lead_Distribution__c tempLeadDist: All_Lead_Distribution__c.getall().values())
        {
            if(tempLeadDist.Active__c)
                queueList.put(tempLeadDist.Lead_Queue_ID__c,tempLeadDist.Name);
        }
        
        List<Lead> queueLeads = [Select l.OwnerId, l.Id 
                                 From Lead l 
                                 where l.OwnerId in :queueList.Keyset() 
                                 order by Lead_Assigned_Date__c Asc];
	   	Set<string> setQueue = queueList.Keyset();
	    List<string> lstQueue = new List<string>();
	    lstQueue.addAll(setQueue);
	    string chk = '\'';
	    integer i = 0;
	    for(string s : lstQueue) {
	   		chk = chk +''+ s + '\'';
	   		i++;	
	   		if(i<lstQueue.size())
	   			chk = chk + 'or l.OwnerId = \'';
	   }                                 
       query = 'Select l.OwnerId, l.Id From Lead l where l.OwnerId = ' + chk + 'order by Lead_Assigned_Date__c Asc';
    }

    global Database.QueryLocator start(Database.BatchableContext BC){        
       return Database.getQueryLocator(query);
    }
	
	//Code to pull out the corresponding members and assigning them leads in a round robin fashion.
    global void execute(Database.BatchableContext BC, List<Lead> queueLeads){
    	//Assigning leads to the queue Members.
    	
    	Datetime oldestAssignment = system.now(); 
    	//Excutes only if leads are present which need to be assigned.
    	if (queueLeads.size() != 0) {
    		//Set of lead Owner Ids which are mainly queues to create the queue object.
        	set<string> qLeads = new set<string>();
	        for(Lead l : queueLeads) {
	            qLeads.add(l.OwnerId);
	        } 
	        
	        List<GroupMember> grpMembers = [Select UserOrGroupId,GroupId 
	                                        From GroupMember 
	                                        where GroupId in :qLeads];
	        List<Lead_Owner_Assignment__c> lstLeadOwners = [Select  l.Id, l.User_Queue_Id__c,l.Most_Recent_Lead_Assigned_Date__c, l.Lead_Owner__c 
	                                                        From Lead_Owner_Assignment__c l 
	                                                        where l.User_Queue_Id__c in :qLeads
	                                                        order by l.Most_Recent_Lead_Assigned_Date__c asc];
	        //Creation of map which has queue Id and corresponding Queue Object.
	        Map<String, Queue> queueMap = new Map<String, Queue>();
	                
	        for (Lead l : queueLeads) {
	            Queue q = queueMap.get(l.OwnerId);
	            if (q == null) {
	                q = new Queue();
	                queueMap.put(l.OwnerId, q);
	            }
	            q.leads.add(l.Id);
	        }
	        
	        for (GroupMember gm : grpMembers) {
	        	system.debug('Here group members');
	            Queue q = queueMap.get(gm.GroupId);
	            system.debug(gm.UserOrGroupId);
	            string sId = gm.UserOrGroupId;
	            system.debug(sId.substring(0,3));
	            if(!q.members.contains(gm.UserOrGroupId) && sId.substring(0,3).equals(userPrefix)  ) {//
	            	system.debug(sId.substring(0,3).equals(userPrefix));
	                q.members.add(gm.UserOrGroupId); 
	                system.debug('add members to object');                       
	            }       
	        }
	        
	        for (Lead_Owner_Assignment__c loa : lstLeadOwners) {
	            Queue q = queueMap.get(loa.User_Queue_Id__c);
	            if (q.members.contains(loa.Lead_Owner__c)) {
	                q.assignments.add(loa);
	                q.membersWithAssignment.add(loa.Lead_Owner__c);
	            }
	            if (loa.Most_Recent_Lead_Assigned_Date__c <= oldestAssignment)
	                oldestAssignment = loa.Most_Recent_Lead_Assigned_Date__c;       
	        }
	        
	        for (GroupMember gm : grpMembers) {
	            Queue q = queueMap.get(gm.GroupId);
	            string sId = gm.UserOrGroupId;
	            if(!q.membersWithAssignment.contains(gm.UserOrGroupId)&& sId.substring(0,3).equals(userPrefix) ) {//
	                Lead_Owner_Assignment__c loa = new Lead_Owner_Assignment__c();
	                loa.User_Queue_Id__c = gm.GroupId;
	                loa.Lead_Owner__c = gm.UserOrGroupId;
	                loa.Most_Recent_Lead_Assigned_Date__c = oldestAssignment.addHours(-1);
	                if(q.assignments.size()>0)
	                	q.assignments.add(0,loa);
	                else
	                	q.assignments.add(loa);
	                lstLeadOwners.add(loa); 
	            }
	        } 
	        
	        for (Lead l : queueLeads) {
	            Queue q = queueMap.get(l.OwnerId);
	            q.AssignLead(l);
	        }
	        
	        upsert lstLeadOwners;
	        update queueLeads;   
    	} 
    }

    global void finish(Database.BatchableContext BC){
    }
   
	/**
	 * This class mainly has the lead ids belonging to particular queue,
	 * queue members and their Lead owner assignment object list belonging to the same queue.
	 * It also assigns leads to a queue member.
	 */   
    private class Queue {
        public Set<String> leads { get; set; }
        public Set<String> members { get; set; }
        public Set<String> membersWithAssignment { get; set; }
        public List<Lead_Owner_Assignment__c> assignments { get; set; }
        public Integer index { get; set; }
        
        private DateTime leadAssignDate;
        
        public Queue() {
            leads = new Set<String>();
            members = new Set<String>();
            membersWithAssignment = new Set<String>();
            assignments = new List<Lead_Owner_Assignment__c>();
            index = 0;
            leadAssignDate = Datetime.now();
        }
        
		//Assign leads to the queue memeber depending on the index.     
        public void AssignLead(Lead nLead) {
            Lead_Owner_Assignment__c assignment = assignments[index];
            if(assignment != null) {
            	assignment.Most_Recent_Lead_Assigned_Date__c = leadAssignDate;
              	leadAssignDate = leadAssignDate.addSeconds(1);
            	nLead.OwnerId = assignment.Lead_Owner__c;
            	index += 1;
            	if (index >= assignments.size())
                	index = 0;
            }
        }
    }
}